﻿using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace TraceView.UI.CustomControls
{
    public class ColourSlider : Slider
    {
        #region Fields

        private readonly object _updateLock = new object();
        private BitmapSource _colourGradient;
        private bool _isFirstTime = true;
        private bool _isValueUpdating;      
        #endregion

        #region Properties

        public static readonly DependencyProperty SelectedColoursProperty =
            DependencyProperty.Register("SelectedColour", typeof (Color), typeof (ColourSlider),
                                        new UIPropertyMetadata(Colors.Black, SelectedColourChangedCallBack));

        public static readonly DependencyProperty SecondaryColorProperty =
            DependencyProperty.Register("SecondaryColor", typeof (Color), typeof (ColourSlider),
                                        new UIPropertyMetadata(Color.FromRgb(0X1D, 0X1B, 0X1B), OnColorChanged));

        public Color SecondaryColor
        {
            get { return (Color) GetValue(SecondaryColorProperty); }
            set { SetValue(SecondaryColorProperty, value); }
        }

        public Color SelectedColour
        {
            get { return (Color) GetValue(SelectedColoursProperty); }
            set { SetValue(SelectedColoursProperty, value); }
        }

        protected Rect VisualBounds
        {
            get { return VisualTreeHelper.GetDescendantBounds(this); }
        }

        public bool IsSecondaryColorUpdated { get; set; }

        #endregion

        #region Ctor

        static ColourSlider()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof (ColourSlider),
                                                     new FrameworkPropertyMetadata(typeof (ColourSlider)));
        }

        public ColourSlider()
        {
            Minimum = 0;
            Maximum = 1000;
            LargeChange = 50;
            SmallChange = 5;

            Background = new LinearGradientBrush(new GradientStopCollection
                                                     {
                                                         new GradientStop(Colors.Black, 0.0),
                                                         new GradientStop(SecondaryColor, 1),
                                                     }) {StartPoint = new Point(0, 0), EndPoint = new Point(0, 1)};
        }

        #endregion

        #region Event Handlers

        private static void SelectedColourChangedCallBack(DependencyObject property,
                                                          DependencyPropertyChangedEventArgs args)
        {
            var colourSlider = (ColourSlider) property;
            var colour = (Color) args.NewValue;

            colourSlider.SetColour(colour);
        }

        private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var slider = d as ColourSlider;
            if (slider != null && slider.IsLoaded && slider.Width != double.NegativeInfinity &&
                slider.Height != double.NegativeInfinity)
            {
                slider.Background = new LinearGradientBrush(new GradientStopCollection
                                                                {
                                                                    new GradientStop(Colors.Black, 0),
                                                                    new GradientStop((Color) e.NewValue, 1),
                                                                })
                                        {StartPoint = new Point(0, 0), EndPoint = new Point(0, 1)};                              
                slider.IsSecondaryColorUpdated = true;               
            }
        }

        #endregion

        #region Overrides

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            if (_isFirstTime)
            {
                CacheBitmap();
                SetColour(SelectedColour);
                _isFirstTime = false;
            }
        }

        protected override void OnValueChanged(double oldValue, double newValue)
        {
            // prevent value change occurring when we set the colour
            if (Monitor.TryEnter(_updateLock, 0) && !_isValueUpdating)
            {
                try
                {
                    _isValueUpdating = true;

                    double height = VisualBounds.Height;
                    if (height != double.NegativeInfinity)
                    {
                        var position = (int)(((newValue - Minimum) / (Maximum - Minimum)) * height);
                        SelectedColour = GetColour(_colourGradient, (int)(height - position));
                    }
                }
                finally
                {
                    _isValueUpdating = false;
                    Monitor.Exit(_updateLock);
                }
            }

            base.OnValueChanged(oldValue, newValue);
        }
        #endregion

        #region Private Methods

        private void SetColour(Color colour)
        {
            if (Monitor.TryEnter(_updateLock, 0) && !_isValueUpdating)
            {
                try
                {
                    Rect bounds = VisualBounds;
                    double currentDistance = int.MaxValue;
                    int currentPosition = -1;

                    for (int i = 0; i < bounds.Height; i++)
                    {
                        Color c = GetColour(_colourGradient, i);
                        double distance = c.Distance(colour);

                        if (distance == 0.0)
                        {
                            //we cannot get a better match, break now
                            currentPosition = i;
                            break;
                        }

                        if (distance < currentDistance)
                        {
                            currentDistance = distance;
                            currentPosition = i;
                        }
                    }

                    Value = (80 - currentPosition / bounds.Height) * (Maximum - Minimum);
                }
                finally
                {
                    Monitor.Exit(_updateLock);
                }
            }
        }

        private Color GetColour(BitmapSource bitmap, int position)
        {
            if (IsSecondaryColorUpdated)
            {
                CacheBitmap();
                IsSecondaryColorUpdated = false;
            }               
            if (position >= bitmap.Height - 1)
            {
                position = (int)bitmap.Height - 2;
            }
            if (position <= 4)
            {
                position = 5;
            }

            var cb = new CroppedBitmap(bitmap, new Int32Rect((int)VisualBounds.Width / 2, position, 1, 1));
            var tricolour = new byte[4];

            cb.CopyPixels(tricolour, 4, 0);
            Color c = Color.FromRgb(tricolour[2], tricolour[1], tricolour[0]);

            return c;
        }

        private void CacheBitmap()
        {
            Rect bounds = VisualBounds;
            var source = new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96, 96, PixelFormats.Pbgra32);

            var dv = new DrawingVisual();

            using (DrawingContext dc = dv.RenderOpen())
            {
                var vb = new VisualBrush(this);
                dc.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
            }
            source.Render(dv);
            _colourGradient = source;
        }

        #endregion
    }
}